Skip to content

Comments

refactor: FailedBookingsByField to use Insights Routing Service#23259

Merged
hbjORbj merged 10 commits intomainfrom
devin/failed-bookings-insights-routing-1755787872
Aug 28, 2025
Merged

refactor: FailedBookingsByField to use Insights Routing Service#23259
hbjORbj merged 10 commits intomainfrom
devin/failed-bookings-insights-routing-1755787872

Conversation

@eunjae-lee
Copy link
Contributor

@eunjae-lee eunjae-lee commented Aug 21, 2025

What does this PR do?

Refactors the FailedBookingsByField component to use the centralized InsightsRoutingService instead of the legacy authorization method, following the same pattern established in PR #23031.

It also fixes the bug that the previous implementation was not displaying the result for the selected routing form but for all the forms.

Visual Demo (For contributors especially)

image

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • N/A - I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

⚠️ Critical: This PR has not been tested locally and requires human verification

  1. Access the Insights page with routing forms data in your development environment
  2. Navigate to the FailedBookingsByField component (should display failed booking statistics grouped by form fields)
  3. Test date filtering: Change the date range and verify that:
    • Data updates correctly when dates change
    • Component shows/hides appropriately when no data exists for the selected period
  4. Test team/org filtering: Switch between different teams/organizations and verify data updates
  5. Test routing form filtering: If available, test filtering by specific routing forms
  6. Verify data format: Ensure the grouped data structure displays correctly in the UI

Environment setup:

  • Requires routing forms with failed booking responses (responses without routedToBookingUid)
  • Test data should include multiple forms, fields, and options for comprehensive testing

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

🔍 Review Focus Areas

High Priority:

  1. SQL Query Accuracy - Verify the complex CTE query was migrated correctly from routing-events.ts to InsightsRoutingBaseService
  2. Authorization Equivalence - Confirm getBaseConditions() provides same access control as the original manual team filtering
  3. Parameter Mapping - Validate that scope/selectedTeamId correctly replace isAll/teamId/userId parameters
  4. Date Filtering Logic - Review the added date filtering implementation in the SQL query
  5. Data Format Preservation - Ensure return format matches exactly what the UI component expects

Testing Required:

  • Local testing is essential as this PR was not tested in a development environment
  • Focus on data accuracy, authorization boundaries, and UI functionality

Link to Devin run: https://app.devin.ai/sessions/9f69c66f0ff74b7a86ffbca3ab992480
Requested by: @eunjae-lee

- Move getFailedBookingsByRoutingFormGroup logic to InsightsRoutingBaseService
- Replace legacy getWhereForTeamOrAllTeams with getBaseConditions()
- Add failedBookingsByFieldInputSchema with date filtering support
- Update tRPC endpoint to use createInsightsRoutingService pattern
- Update component to pass startDate/endDate parameters
- Remove legacy method from routing-events.ts

Follows same pattern as PR #23031 for RoutedToPerPeriod refactoring

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 21, 2025

Walkthrough

UI: FailedBookingsByField.tsx now imports and uses useInsightsRoutingParameters(), passing the returned object directly into trpc.viewer.insights.failedBookingsByField.useQuery.
Server: Removed RoutingEventsInsights.getFailedBookingsByRoutingFormGroup.
TRPC: failedBookingsByField input switched to insightsRoutingServiceInputSchema and now calls createInsightsRoutingService(ctx, input).getFailedBookingsByFieldData() inside a try/catch that throws TRPCError on failure.
Service: InsightsRoutingBaseService aliases routing denormalized columns with rfrd and adds getFailedBookingsByFieldData(), which runs the aggregation query and returns nested form → field → option counts.

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/failed-bookings-insights-routing-1755787872

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@keithwillcode keithwillcode added consumer core area: core, team members only labels Aug 21, 2025
- Add missing import for useInsightsRoutingParameters
- Update component to follow same pattern as PR #23031
- Add columnFilters support to failedBookingsByFieldInputSchema
- Maintain routingFormId parameter from useInsightsParameters

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@vercel
Copy link

vercel bot commented Aug 21, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Aug 25, 2025 9:58am
cal-eu Ignored Ignored Aug 25, 2025 9:58am

devin-ai-integration bot and others added 4 commits August 21, 2025 15:14
- Remove routingFormId parameter from getFailedBookingsByFieldData method
- Update component to only use insightsRoutingParameters
- Remove routingFormId from failedBookingsByFieldInputSchema
- Let getBaseConditions handle all filtering including routing form filtering
- Simplify tRPC endpoint to not pass separate routingFormId

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
…okingsByFieldData

- Replace complex CTE approach with direct denormalized table query
- Join with App_RoutingForms_Form to access field definitions from JSON
- Use RoutingFormResponseField for response values
- Filter failed bookings with bookingUid IS NULL
- Maintain same data structure for UI compatibility
- Follow existing service patterns for authorization and date filtering

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@eunjae-lee
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
packages/features/insights/server/trpc-router.ts (2)

890-895: Preserve error context or add logging for debuggability.

Catching and rethrowing INTERNAL_SERVER_ERROR loses the root cause. Consider logging the original error (with safe metadata) or attaching a minimal message for observability.

Apply this small diff:

-      try {
-        return await insightsRoutingService.getFailedBookingsByFieldData();
-      } catch (e) {
-        throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
-      }
+      try {
+        return await insightsRoutingService.getFailedBookingsByFieldData();
+      } catch (e) {
+        ctx.logger?.error?.({ err: e, route: "viewer.insights.failedBookingsByField" }, "failedBookingsByField error");
+        throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
+      }

888-896: (Optional) Guardrail: enforce presence of routing form filter at the edge.

UI currently guarantees a routingFormId, but adding a lightweight guard prevents accidental regressions that could reintroduce the “all forms” bug.

Proposed diff:

   .query(async ({ ctx, input }) => {
     const insightsRoutingService = createInsightsRoutingService(ctx, input);
+    const hasFormIdFilter =
+      (input.columnFilters || []).some((f) => f.id === "formId" && !!(f as any).value?.data);
+    if (!hasFormIdFilter) {
+      // Defensive: UI should always pass a formId, keep server strict.
+      throw new TRPCError({ code: "BAD_REQUEST", message: "routingFormId is required" });
+    }
     try {
       return await insightsRoutingService.getFailedBookingsByFieldData();
     } catch (e) {
       throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
     }
   })
packages/features/insights/components/routing/FailedBookingsByField.tsx (1)

134-136: Pass-through of insightsRoutingParams to TRPC matches the new endpoint; consider enabling only when formId exists.

The query call is correct. As a minor guard, you could enable the query only when the hook has emitted a formId to avoid any early, empty fetch cycles.

Optional diff:

-  const { data } = trpc.viewer.insights.failedBookingsByField.useQuery(insightsRoutingParams);
+  const enabled = (insightsRoutingParams.columnFilters || []).some((f) => f.id === "formId");
+  const { data } = trpc.viewer.insights.failedBookingsByField.useQuery(insightsRoutingParams, { enabled });
packages/lib/server/service/InsightsRoutingBaseService.ts (3)

436-437: Qualifying filters with rfrd. is correct; minor robustness tweaks possible.*

  • The aliasing fixes are spot-on and ensure filters hit the denormalized source consistently.
  • For bookingUserId ANY, consider coercing to numeric to avoid accidental text[] bindings from UI.

Proposed diff:

-      conditions.push(Prisma.sql`rfrd."bookingUserId" = ANY(${memberUserIds.value.data})`);
+      conditions.push(Prisma.sql`rfrd."bookingUserId" = ANY(${memberUserIds.value.data.map(Number)})`);

If bookingUserId filter values can be strings, this prevents a Postgres type mismatch. If they’re guaranteed numbers upstream, feel free to skip.

Also applies to: 453-454, 462-463, 471-472, 505-506, 513-514


760-796: Sort options within each field for clearer UX; ORDER BY in SQL is redundant post-aggregation.

  • After grouping, options per field aren’t sorted; sort descending by count to show most frequent failures first.
  • Since you re-aggregate in TS, the SQL ORDER BY count DESC is not required; removing it can save the planner unnecessary work.

Apply this diff:

-    const groupedByFormAndField = result.reduce((acc, curr) => {
+    const groupedByFormAndField = result.reduce((acc, curr) => {
       const formKey = curr.formName;
       acc[formKey] = acc[formKey] || {};
       const labelKey = curr.fieldLabel;
       acc[formKey][labelKey] = acc[formKey][labelKey] || [];
       acc[formKey][labelKey].push({
         optionId: curr.optionId,
         count: curr.count,
         optionLabel: curr.optionLabel,
       });
       return acc;
     }, {} as Record<string, Record<string, { optionId: string; count: number; optionLabel: string }[]>>);
+
+    // Sort options within each field by count desc for better readability
+    for (const fields of Object.values(groupedByFormAndField)) {
+      for (const label of Object.keys(fields)) {
+        fields[label] = fields[label].sort((a, b) => b.count - a.count);
+      }
+    }

Optionally remove the SQL ORDER BY:

-    WHERE ff.option_id IS NOT NULL
-    ORDER BY count DESC
+    WHERE ff.option_id IS NOT NULL

697-796: Keying by formName risks collisions if multiple forms share the same name (when formId filter is absent).

UI currently enforces a routingFormId, so you’re safe. If that ever changes, consider keying by formId and passing formName alongside to avoid conflation.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f3d4eab and 36d015a.

📒 Files selected for processing (4)
  • packages/features/insights/components/routing/FailedBookingsByField.tsx (2 hunks)
  • packages/features/insights/server/routing-events.ts (0 hunks)
  • packages/features/insights/server/trpc-router.ts (1 hunks)
  • packages/lib/server/service/InsightsRoutingBaseService.ts (9 hunks)
💤 Files with no reviewable changes (1)
  • packages/features/insights/server/routing-events.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/insights/server/trpc-router.ts
  • packages/lib/server/service/InsightsRoutingBaseService.ts
  • packages/features/insights/components/routing/FailedBookingsByField.tsx
🧠 Learnings (8)
📓 Common learnings
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/RoutingFunnel.tsx:15-17
Timestamp: 2025-07-15T12:58:40.539Z
Learning: In the insights routing funnel component (packages/features/insights/components/RoutingFunnel.tsx), the useColumnFilters exclusions are intentionally different from the general useInsightsParameters exclusions. RoutingFunnel specifically excludes only ["createdAt"] while useInsightsParameters excludes ["bookingUserId", "formId", "createdAt", "eventTypeId"]. This difference is by design.
📚 Learning: 2025-07-15T12:59:34.389Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

Applied to files:

  • packages/features/insights/server/trpc-router.ts
  • packages/lib/server/service/InsightsRoutingBaseService.ts
  • packages/features/insights/components/routing/FailedBookingsByField.tsx
📚 Learning: 2025-08-17T22:00:16.329Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts:117-126
Timestamp: 2025-08-17T22:00:16.329Z
Learning: In calcom/cal.com PR #22995, packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts, the enabled input parameter in the update endpoint is intentionally not forwarded to aiService.updateAgentConfiguration() as the enabled/disabled agent functionality is not required at the moment (per maintainer Udit-takkar). Future reviews should not flag this as missing functionality unless requirements change.

Applied to files:

  • packages/features/insights/server/trpc-router.ts
📚 Learning: 2025-08-14T10:48:52.586Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/trpc/server/routers/viewer/ai/_router.ts:46-84
Timestamp: 2025-08-14T10:48:52.586Z
Learning: In calcom/cal.com PR #22995, packages/trpc/server/routers/viewer/ai/_router.ts, the voiceId input parameter in the create endpoint is intentionally not forwarded to aiService.createAgent() as voice customization is not required at the moment (per maintainer Udit-takkar). Future reviews should not flag this as missing functionality unless requirements change.

Applied to files:

  • packages/features/insights/server/trpc-router.ts
📚 Learning: 2025-07-24T08:39:06.185Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22702
File: packages/lib/server/service/insightsBooking.ts:120-124
Timestamp: 2025-07-24T08:39:06.185Z
Learning: In the InsightsBookingService (packages/lib/server/service/insightsBooking.ts), the constructor stores null for invalid options or filters but this is handled safely through null checks in buildFilterConditions() and buildAuthorizationConditions() methods. The service uses defensive programming to return safe fallback conditions (null or NOTHING_CONDITION) rather than throwing errors on invalid inputs.

Applied to files:

  • packages/lib/server/service/InsightsRoutingBaseService.ts
📚 Learning: 2025-07-15T13:02:17.403Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/lib/server/service/insightsRouting.ts:367-368
Timestamp: 2025-07-15T13:02:17.403Z
Learning: In the InsightsRoutingService (packages/lib/server/service/insightsRouting.ts), multi-select filter data is already validated by zod before reaching the buildFormFieldSqlCondition method, so null/undefined values are not present in filterValue.data arrays and don't need to be filtered out.

Applied to files:

  • packages/lib/server/service/InsightsRoutingBaseService.ts
📚 Learning: 2025-07-15T12:58:40.539Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/RoutingFunnel.tsx:15-17
Timestamp: 2025-07-15T12:58:40.539Z
Learning: In the insights routing funnel component (packages/features/insights/components/RoutingFunnel.tsx), the useColumnFilters exclusions are intentionally different from the general useInsightsParameters exclusions. RoutingFunnel specifically excludes only ["createdAt"] while useInsightsParameters excludes ["bookingUserId", "formId", "createdAt", "eventTypeId"]. This difference is by design.

Applied to files:

  • packages/lib/server/service/InsightsRoutingBaseService.ts
  • packages/features/insights/components/routing/FailedBookingsByField.tsx
📚 Learning: 2025-08-19T09:47:49.478Z
Learnt from: eunjae-lee
PR: calcom/cal.com#23166
File: packages/prisma/migrations/20250818151914_routing_form_response_denormalized_backfill2/migration.sql:65-66
Timestamp: 2025-08-19T09:47:49.478Z
Learning: The Booking table has a unique constraint and index on the uid column (defined as `uid String unique` in schema.prisma), so JOINs on Booking.uid have optimal performance and don't require additional indexing.

Applied to files:

  • packages/lib/server/service/InsightsRoutingBaseService.ts
🔇 Additional comments (4)
packages/features/insights/server/trpc-router.ts (1)

888-896: Endpoint refactor to InsightsRoutingService looks correct; input contract aligned.

  • Switching to insightsRoutingServiceInputSchema and delegating to createInsightsRoutingService(ctx, input).getFailedBookingsByFieldData() matches the new service API and fixes the legacy coupling. Looks good.
packages/features/insights/components/routing/FailedBookingsByField.tsx (1)

19-19: Hook swap to useInsightsRoutingParameters is aligned with the new backend input shape.

The import change is correct and consistent with the TRPC schema refactor.

packages/lib/server/service/InsightsRoutingBaseService.ts (2)

566-566: Authorization parity with rfrd aliasing looks right; validate scope semantics against legacy behavior.

  • user: rfrd.formUserId = userId AND rfrd.formTeamId IS NULL
  • org: teamIds OR user’s personal forms
  • team: exact teamId

This matches expectations for routing responses. Please double-check any edge cases where orgId may be null under “user” scope so validation doesn’t null out options.

Would you like a small unit-test harness for getAuthorizationConditions across scopes?

Also applies to: 587-587, 603-604


697-759: CTE accurately computes failed bookings by form/field/option; zero-count options preserved.

  • Good: baseConditions applied; failed defined as bookingUid IS NULL; DISTINCT response counting prevents duplicates; options expanded from form JSON and left-joined to include zero-counts.
  • This directly addresses “selected form only” when formId is present in columnFilters.

@eunjae-lee eunjae-lee marked this pull request as ready for review August 22, 2025 15:12
@graphite-app graphite-app bot requested a review from a team August 22, 2025 15:12
@dosubot dosubot bot added insights area: insights, analytics routing-forms area: routing forms, routing, forms labels Aug 22, 2025
@graphite-app
Copy link

graphite-app bot commented Aug 22, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (08/22/25)

1 reviewer was added to this PR based on Keith Williams's automation.

@github-actions
Copy link
Contributor

github-actions bot commented Aug 22, 2025

E2E results are ready!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts (1)

593-600: Order-dependent assertion without ORDER BY can flake.

SELECT id ... WHERE ${baseConditions} has no ORDER BY, yet the test asserts fixed positions (results[0], results[1]). This can intermittently fail.

Apply this diff to make the assertion order-agnostic (mirroring your later test):

-      // Should only return the authorized user's form response
-      expect(results).toHaveLength(2);
-      expect(results[0]?.id).toBe(testData.formResponse.id);
-      expect(results[1]?.id).toBe(personalFormResponse.id);
+      // Should return both the user's team form response and the personal form response (order-agnostic)
+      expect(results).toHaveLength(2);
+      const responseIds = results.map((r) => r.id).sort();
+      const expectedIds = [testData.formResponse.id, personalFormResponse.id].sort();
+      expect(responseIds).toEqual(expectedIds);

Also update the preceding comment to reflect that two rows are expected, not “only” one.

🧹 Nitpick comments (6)
packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts (6)

912-915: Enum source-of-truth for bookingStatusOrder and bookingUserId filters.

The tests use string literals (e.g., "pending", "accepted"). If bookingStatusOrder maps to an enum, prefer referencing the enum constants in test construction to catch drift early. For user IDs, ensure the ANY array is typed (see earlier comment).

If bookingStatusOrder is an enum, consider:

  • Using BookingStatus.PENDING/ACCEPTED (if values match), or
  • A mapping helper to translate enum to stored string in the denormalized view.

Also applies to: 1029-1032


1067-1076: Subqueries for attendee filters: correctness good; ensure indexes for performance.

The EXISTS subqueries joining Booking and Attendee look correct and safe (parameterized ILIKE). For production scale, confirm indexes exist on:

  • Attendee.bookingId
  • Attendee.name, Attendee.email, Attendee.phoneNumber (consider trigram or lower(text) indexes if these predicates are hot)

Would you like me to propose index DDLs based on current query patterns?

Also applies to: 1111-1120, 1155-1164


1247-1255: Multi-select semantics: confirm “match all” (@>) vs “overlap any” (&&).

Using @> means the response must contain all selected options. If the UI’s multi-select filter is intended as “any of” semantics, use the overlap operator (&&) instead.

If “any-of” is desired, adapt the expectation to:

-AND rrf."valueStringArray" @> ${["option1", "option2"]}
+AND rrf."valueStringArray" && ${Prisma.sql`ARRAY[${Prisma.join(["option1","option2"])}]`}::text[]

And mirror the same in the service implementation.


1306-1316: Brittle parentheses in combined condition expectations.

The nested parentheses make tests sensitive to harmless formatting changes in the builder. Consider composing with intermediate fragments and a normalizer or assert via substring presence of each conjunct.

Example approach inside the test:

  • Build expected parts individually (date, status, reason, custom field).
  • Combine via Prisma.sql${part1} AND ${part2} AND ${part3} to reduce nesting depth.

90-104: Make field IDs consistent between form definition and response payload.

You generate random IDs for fields, then use different random IDs as keys in the response. This works for current tests (as they don’t assert field-level joins here) but can break future tests or denormalization assumptions. Reuse the same IDs for both.

Apply this refactor:

@@
-  const form = await prisma.app_RoutingForms_Form.create({
+  const textFieldId = `text-field-id-${randomUUID()}`;
+  const emailFieldId = `email-field-id-${randomUUID()}`;
+  const form = await prisma.app_RoutingForms_Form.create({
     data: {
       name: "Test Routing Form",
       userId: user.id,
       teamId: team.id,
       fields: [
         {
-          id: `text-field-id-${randomUUID()}`,
+          id: textFieldId,
           type: "text",
           label: "Name",
           required: true,
         },
         {
-          id: `email-field-id-${randomUUID()}`,
+          id: emailFieldId,
           type: "email",
           label: "Email",
           required: true,
         },
       ],
     },
   });
@@
-  const formResponse = await prisma.app_RoutingForms_FormResponse.create({
+  const formResponse = await prisma.app_RoutingForms_FormResponse.create({
     data: {
       formFillerId: `test-filler-${randomUUID()}`,
       formId: form.id,
       response: {
-        [`text-field-id-${randomUUID()}`]: {
+        [textFieldId]: {
           label: "Name",
           value: "Test Name",
         },
-        [`email-field-id-${randomUUID()}`]: {
+        [emailFieldId]: {
           label: "Email",
           value: "test@example.com",
         },
       },
       routedToBookingUid: booking.uid,
     },
   });

Also applies to: 113-121


249-257: Broaden coverage: add tests for getFailedBookingsByFieldData() output shape.

This file thoroughly tests auth/filter fragments. Given this PR introduces getFailedBookingsByFieldData(), consider adding at least one integration test asserting the nested form → field → option counts shape and date filtering respects rfrd aliasing.

I can draft a minimal test that:

  • Creates one form with two fields and two responses, one failed (no routedToBookingUid), one successful.
  • Calls service.getFailedBookingsByFieldData() with routingFormId filter and date bounds.
  • Asserts aggregation counts and that only the selected form is included.
    Would you like me to open a follow-up PR with this test?

Also applies to: 516-544

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 36d015a and 1963c59.

📒 Files selected for processing (1)
  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts (25 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/RoutingFunnel.tsx:15-17
Timestamp: 2025-07-15T12:58:40.539Z
Learning: In the insights routing funnel component (packages/features/insights/components/RoutingFunnel.tsx), the useColumnFilters exclusions are intentionally different from the general useInsightsParameters exclusions. RoutingFunnel specifically excludes only ["createdAt"] while useInsightsParameters excludes ["bookingUserId", "formId", "createdAt", "eventTypeId"]. This difference is by design.
📚 Learning: 2025-08-22T16:38:00.182Z
Learnt from: bandhan-majumder
PR: calcom/cal.com#23192
File: packages/lib/server/service/InsightsBookingBaseService.ts:814-816
Timestamp: 2025-08-22T16:38:00.182Z
Learning: In the InsightsBookingBaseService (packages/lib/server/service/InsightsBookingBaseService.ts), when filtering for "accepted" bookings in getMembersStatsWithCount(), using `endTime <= now()` in the SQL condition should be avoided as it conflicts with existing date filtering logic. The components already handle completion filtering by setting `endDate: currentTime` in their query parameters, making additional SQL-level endTime filtering unnecessary and potentially problematic.

Applied to files:

  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts
🔇 Additional comments (7)
packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts (7)

326-329: Good aliasing and user-scope condition assertion.

Using the rfrd alias consistently and asserting the exact user-scope condition looks correct and aligns with the service refactor.


351-351: Team-scope condition LGTM.

The equality on rfrd."formTeamId" is precise and matches the intended authorization boundary for team scope.


478-481: Caching assertion is solid.

Confirming identical Sql fragments on repeat calls is a good proxy for cache correctness.


524-540: Readable composition of base conditions.

Binding dateCondition separately and composing it into the final Sql keeps intent clear and reduces duplication. Good pattern.


1202-1210: Custom field TEXT filter LGTM.

EXISTS on RoutingFormResponseField by fieldId with valueString equality is straightforward and matches expectations.


814-823: Time control in columnFilters suite is solid.

Freezing time ensures deterministic date windows. Good practice here.


451-452: Incorrect suggestion regarding timestamp type
The createdAt column on RoutingFormResponseDenormalized is defined in Prisma with @db.Timestamp(3), which maps to a SQL TIMESTAMP(3) without time zone. Casting the test literals to ::timestamp therefore correctly matches the column’s type. Switching to ::timestamptz would introduce a type mismatch rather than fix one. The recommendation assumed a timestamptz column, but the schema shows otherwise—no change is needed here.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@hbjORbj hbjORbj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful!

@hbjORbj hbjORbj merged commit a494428 into main Aug 28, 2025
65 of 67 checks passed
@hbjORbj hbjORbj deleted the devin/failed-bookings-insights-routing-1755787872 branch August 28, 2025 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

consumer core area: core, team members only insights area: insights, analytics ready-for-e2e routing-forms area: routing forms, routing, forms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants